package cn.sytton.taffe.code.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.sytton.taffe.code.entity.CodeEntity;
import cn.sytton.taffe.code.entity.ColumnEntity;
import cn.sytton.taffe.code.entity.TableEntity;
import cn.sytton.taffe.code.mapper.ColumnMapper;
import cn.sytton.taffe.code.mapper.TableMapper;
import cn.sytton.taffe.code.util.PathUtil;
import cn.sytton.taffe.code.util.VelocityInitUtil;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.StringWriter;
import java.sql.Types;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 自动生成代码服务层实现
 *
 * @author skyrock
 */
@Service
public class CodeService {

    @Value("${code.project-name}")
    private String projectName;

    @Value("${db.master.url}")
    private String defaultUrl;

    @Value("${db.master.username}")
    private String defaultUsername;

    @Value("${db.master.password}")
    private String defaultPassword;

    @Value("${code.db.master.url:}")
    private String url;

    @Value("${code.db.master.username:}")
    private String username;

    @Value("${code.db.master.password:}")
    private String password;

    @Resource
    private TableMapper tableMapper;

    @Resource
    private ColumnMapper columnMapper;

    public Map<String, String> generate(CodeEntity entity) {
//        if (!StringUtils.hasText(javaSrcPath)) {
//            throw new BizException("请配置代码路径code.src-path");
//        }
//        if (!FileUtil.exist(javaSrcPath)){
//            throw new BizException("代码路径不存在");
//        }
        Map<String, String> result;

        result = generateJava (entity);

        VelocityContext params = putParams(entity);
        result.putAll(generateVue(entity, params));
        result.putAll(generateSql(entity, params));

        return result;
    }

    private Map<String, String> generateJava(CodeEntity entity) {
        String tableName = entity.getTableName();
        String prefix = entity.getTablePre();
        String xmlPath = entity.getJavaPath().substring(0, entity.getJavaPath().length() - 4) + "resources"+File.separator+"mapper"+File.separator+prefix;

        url = "".equals(url) ? defaultUrl : url;
        username = "".equals(username) ? defaultUsername : username;
        password = "".equals(password) ? defaultPassword : password;
        FastAutoGenerator.create(
                        "".equals(url) ? defaultUrl : url,
                        "".equals(username) ? defaultUsername : username,
                        "".equals(password) ? defaultPassword : password
                )
                .globalConfig(builder -> {
                    builder.author("".equals(entity.getAuthor()) ? "rock" : entity.getAuthor())
                            .enableSpringdoc()
                            .disableOpenDir()
                            .outputDir(entity.getJavaPath());
                })
                .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    if (typeCode == Types.DATE) {
                        // 自定义类型转换
                        return DbColumnType.DATE;
                    }
                    return typeRegistry.getColumnType(metaInfo);
                }))
                .packageConfig(builder -> {
                    builder.parent(projectName)
                            .moduleName(prefix)
                            .pathInfo(Collections.singletonMap(OutputFile.xml, xmlPath));
                })
                .strategyConfig(builder -> {
                    String superClass = "cn.sytton.taffe.framework.base.entity.BaseEntity";
                    if (entity.isIncludeUpdatedField()){
                        superClass = "cn.sytton.taffe.framework.base.entity.BaseUpdatedEntity";
                    } else if (entity.isIncludeCreatedField()) {
                        superClass = "cn.sytton.taffe.framework.base.entity.BaseCreatedEntity";
                    }
                    builder.addInclude(tableName)
                            .addTablePrefix(prefix+"_")

                            .mapperBuilder()
                            .superClass("cn.sytton.taffe.framework.base.mapper.MasterSqlMapper")

                            .entityBuilder()
                            .superClass(superClass)
                            .addIgnoreColumns("updatedUserId", "updatedTime", "createdUserId", "createdTime")
                            .enableChainModel()
                            .convertFileName(name -> name+"Entity")
                            .idType(IdType.ASSIGN_ID)
                            .enableLombok()
                            .disableSerialVersionUID()

                            .serviceBuilder()
                            .superServiceClass("cn.sytton.taffe.framework.base.service.IBaseService")
                            .superServiceImplClass("cn.sytton.taffe.framework.base.service.BaseService")
                            .convertServiceImplFileName(name -> name+"Service")

                            .controllerBuilder()
                            .superClass("cn.sytton.taffe.framework.base.controller.BaseController")
                            .enableHyphenStyle()
                            .enableRestStyle();
                })
                .templateConfig(builder -> {
                    if (entity.isGenController()) {
                        builder.controller("/vm/java/controller.java.vm");
                    } else {
                        builder.controller(null);
                    }

                    if (entity.isGenService()) {
                        builder.serviceImpl("/vm/java/serviceImpl.java.vm");
                    } else {
                        builder.service(null);
                        builder.serviceImpl(null);
                    }

                    if (entity.isGenEntity()) {
                        builder.entity("/vm/java/entity.java.vm");
                    } else {
                        builder.entity(null);
                    }

                    if (!entity.isGenMapper()) {
                        builder.mapper(null);
                    }

                    if (!entity.isGenXml()) {
                        builder.xml(null);
                    }
                })
                .execute();
        String path = entity.getJavaPath() + File.separator + projectName.replace(".", File.separator) + File.separator + entity.getModuleName();
        Map<String, String> result = new LinkedHashMap<>();
        result.put("java", path);
        if (entity.isGenXml()) {
            result.put("xml", xmlPath);
        }
        return result;
    }

    private VelocityContext putParams(CodeEntity entity) {

        TableEntity table = tableMapper.findTable(entity.getTableName()).get(0);
        List<ColumnEntity> columns = columnMapper.findColumn(entity.getTableName());

        String primaryColumn = columns.stream().filter(ColumnEntity::getIsPri).collect(Collectors.toList()).get(0).getName();

        VelocityInitUtil.initVelocity();

        String tableName = table.getTableName().toLowerCase();
        String cnName = table.getTableComment();

        String className = StrUtil.upperFirst(StrUtil.toCamelCase(tableName.substring(tableName.indexOf("_") + 1)));
        String classNameLower = StrUtil.lowerFirst(className);
        String baseUrl = StrUtil.toSymbolCase(className, '-');

        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("moduleName", entity.getModuleName());
        velocityContext.put("cnName", cnName);
        velocityContext.put("className", className);
        velocityContext.put("baseUrl", baseUrl);
        velocityContext.put("classNameLower", classNameLower);
        velocityContext.put("columns", columns);
        velocityContext.put("primaryColumn", primaryColumn);

        //过滤掉主键，非必填，四个基本字段
        List<String> excludeFields = Arrays.asList("createdUserId", "createdTime", "updatedUserId", "updatedTime");
        velocityContext.put("columnsFilter", columns.stream().
                filter(col -> !col.getIsPri()
                        && "NO".equalsIgnoreCase(col.getIsNullAble())
                        && !excludeFields.contains(col.getName()))
                .collect(Collectors.toList()));

        return velocityContext;
    }

    private Map<String, String> generateVue(CodeEntity entity, VelocityContext params) {

        String classNameLower = params.get("classNameLower").toString();

        Map<String, String> result = new LinkedHashMap<>();
        Template tpl;
        if (entity.isGenApi()) {
            StringWriter swApiTs = new StringWriter();
            tpl = Velocity.getTemplate("vm/vue/api.ts.vm", Velocity.ENCODING_DEFAULT);
            tpl.merge(params, swApiTs);
            File apiFile = new File(PathUtil.getTsApiPath(entity.getJsPath(), entity.getModuleName()), classNameLower + ".ts");
            FileUtil.writeString(swApiTs.toString(), apiFile, Velocity.ENCODING_DEFAULT);
            result.put("api.ts", apiFile.getAbsolutePath());
        }
        if (entity.isGenVue()) {
            StringWriter swIndex = new StringWriter();
            tpl = Velocity.getTemplate("vm/vue/index.vue.vm", Velocity.ENCODING_DEFAULT);
            tpl.merge(params, swIndex);
            File indexFile = new File(PathUtil.getVuePath(entity.getJsPath(), entity.getModuleName(), classNameLower), "index.vue");
            FileUtil.writeString(swIndex.toString(), indexFile, Velocity.ENCODING_DEFAULT);
            result.put("index.vue", indexFile.getAbsolutePath());

            StringWriter swForm = new StringWriter();
            tpl = Velocity.getTemplate("vm/vue/Form.vue.vm", Velocity.ENCODING_DEFAULT);
            tpl.merge(params, swForm);
            File formFile = new File(PathUtil.getVuePath(entity.getJsPath(), entity.getModuleName(), classNameLower), "Form.vue");
            FileUtil.writeString(swForm.toString(), formFile, Velocity.ENCODING_DEFAULT);
            result.put("form.vue", formFile.getAbsolutePath());

            StringWriter swHook = new StringWriter();
            tpl = Velocity.getTemplate("vm/vue/hook.ts.vm", Velocity.ENCODING_DEFAULT);
            tpl.merge(params, swHook);
            File hookFile = new File(PathUtil.getVuePath(entity.getJsPath(), entity.getModuleName(), classNameLower), "hook.ts");
            FileUtil.writeString(swHook.toString(), hookFile, Velocity.ENCODING_DEFAULT);
            result.put("hook.ts", hookFile.getAbsolutePath());
        }
        return result;
    }

    private Map<String, String> generateSql(CodeEntity entity, VelocityContext params) {
        Map<String, String> result = new LinkedHashMap<>();
        if (entity.isGenMenuSql()) {
            StringWriter swSql = new StringWriter();
            Template tpl = Velocity.getTemplate("vm/sql/menu.sql.vm", Velocity.ENCODING_DEFAULT);
            tpl.merge(params, swSql);
            result.put("sql", swSql.toString());
        }
        return result;
    }
}
